package com.open.center.jpa.util;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;

import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.Expression;
import jakarta.persistence.criteria.Path;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import lombok.extern.slf4j.Slf4j;

import org.springframework.data.jpa.domain.Specification;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import com.open.center.common.constant.Enumerator;
import com.open.center.common.constant.OpenConstant;
import com.open.center.jpa.model.page.Query;
import com.open.center.jpa.model.page.by.DateTimeBy;
import com.open.center.jpa.model.page.by.FilterBy;
import com.open.center.jpa.model.page.by.LongRangeBy;
import com.open.center.jpa.model.page.by.SearchBy;


/**
 * JPA通用检索工具
 *
 * @author Riche's
 * @since 2025/05/23
 */
@Slf4j
public class SpecificationUtils {


    private static final DateTimeFormatter FORMATTER
            = DateTimeFormatter.ofPattern(OpenConstant.DATE_TIME_FORMAT);

    private SpecificationUtils() {

    }

    public static <T> Specification<T> specification(Query query) {
        return (root, criteriaQuery, builder) -> {
            List<Predicate> predicates = new ArrayList<>();

            // 模糊查询
            searchBys(root, builder, predicates, query.getSearchBys());

            // 精确查询
            filterBys(root, builder, predicates, query.getFilterBys());

            // 日期查询
            dateTimeBys(root, builder, predicates, query.getDateTimeBys());

            // Long类型范围查询
            longRangeBys(root, builder, predicates, query.getLongRangeBys());

            if (criteriaQuery == null) {
                return null;
            }
            return criteriaQuery.where(predicates.toArray(new Predicate[0])).getRestriction();
        };
    }

    public static <T> void searchBys(Root<T> root, CriteriaBuilder builder, List<Predicate> predicates,
                                     List<SearchBy<?>> searchBys) {
        if (!CollectionUtils.isEmpty(searchBys)) {
            searchBys.forEach(by -> {
                if (canSearch(by)) {
                    searchBy(root, builder, predicates, by);
                }
            });
        }
    }

    public static <T> void searchBy(Root<T> root, CriteriaBuilder builder, List<Predicate> predicates, SearchBy<?> searchBy) {
        List<Predicate> likePredicates = new ArrayList<>();
        for (Object value : searchBy.getValues()) {
            // 获取需要搜索的字段路径
            Path<String> fieldPath = root.get(searchBy.getKey());
            if (Boolean.TRUE.equals(searchBy.getCaseSensitive())) {
                // 将字段转换为小写
                Expression<String> lowerField = builder.lower(fieldPath);
                // 将搜索值转换为小写并添加通配符
                String pattern = "%" + value.toString().trim().toLowerCase() + "%";
                likePredicates.add(builder.like(lowerField, pattern));
            } else {
                likePredicates.add(builder.like(fieldPath, "%" + value.toString().trim() + "%"));
            }
        }
        predicates.add(builder.or(likePredicates.toArray(new Predicate[0])));
    }

    public static <T> void filterBys(Root<T> root, CriteriaBuilder builder, List<Predicate> predicates,
                                     List<FilterBy<?>> filterBys) {
        if (!CollectionUtils.isEmpty(filterBys)) {
            filterBys.forEach(by -> {
                if (canFilter(by)) {
                    filterBy(root, builder, predicates, by);
                }
            });
        }
    }

    public static <T> void dateTimeBys(Root<T> root, CriteriaBuilder builder, List<Predicate> predicates,
                                       List<DateTimeBy> dateTimeBys) {
        if (!CollectionUtils.isEmpty(dateTimeBys)) {
            dateTimeBys.forEach(by ->
                    dateTimeBy(root, builder, predicates, by)
            );
        }
    }


    public static <T> void longRangeBys(Root<T> root, CriteriaBuilder builder, List<Predicate> predicates,
                                        List<LongRangeBy> longRangeBys) {
        if (!CollectionUtils.isEmpty(longRangeBys)) {
            longRangeBys.forEach(by ->
                    longRangeBy(root, builder, predicates, by)
            );
        }
    }

    public static <T> void filterBy(Root<T> root, CriteriaBuilder builder, List<Predicate> predicates, FilterBy<?> filterBy) {
        Path<String> fieldPath = root.get(filterBy.getKey());
        CriteriaBuilder.In<Object> inClause;
        if (Boolean.TRUE.equals(filterBy.getCaseSensitive())) {
            inClause = builder.in(builder.lower(fieldPath));
        } else {
            inClause = builder.in(fieldPath);
        }
        for (Object value : filterBy.getValues()) {
            if (value instanceof Enumerator<?, ?>) {
                inClause.value(((Enumerator<?, ?>) value).getId());
            } else {
                if (Boolean.TRUE.equals(filterBy.getCaseSensitive())) {
                    inClause.value(value.toString().toLowerCase());
                } else {
                    inClause.value(value);
                }
            }
        }
        predicates.add(inClause);
    }

    public static <T> void dateTimeBy(Root<T> root, CriteriaBuilder builder, List<Predicate> predicates, DateTimeBy by) {
        if (by != null && StringUtils.hasText(by.getKey())) {
            if (by.getBeginTime() != null && by.getEndTime() != null) {
                LocalDateTime beginTime = LocalDateTime.parse(by.getBeginTime(), FORMATTER);
                LocalDateTime endDTime = LocalDateTime.parse(by.getEndTime(), FORMATTER);
                predicates.add(builder.between(root.get(by.getKey()), beginTime, endDTime));
            } else if (by.getBeginTime() != null) {
                LocalDateTime beginTime = LocalDateTime.parse(by.getBeginTime(), FORMATTER);
                predicates.add(builder.greaterThanOrEqualTo(root.get(by.getKey()), beginTime));
            } else if (by.getEndTime() != null) {
                LocalDateTime endTime = LocalDateTime.parse(by.getEndTime(), FORMATTER);
                predicates.add(builder.lessThan(root.get(by.getKey()), endTime));
            }
        }
    }

    public static <T> void longRangeBy(Root<T> root, CriteriaBuilder builder, List<Predicate> predicates, LongRangeBy longRangeBy) {
        if (StringUtils.hasText(longRangeBy.getKey())) {
            if (longRangeBy.getBeginValue() != null && longRangeBy.getEndValue() != null) {
                predicates.add(builder.between(root.get(longRangeBy.getKey()),
                        longRangeBy.getBeginValue(), longRangeBy.getEndValue()));
            } else if (longRangeBy.getBeginValue() != null) {
                predicates.add(builder.ge(root.get(longRangeBy.getKey()), longRangeBy.getBeginValue()));
            } else if (longRangeBy.getEndValue() != null) {
                predicates.add(builder.le(root.get(longRangeBy.getKey()), longRangeBy.getEndValue()));
            }
        }
    }

    public static boolean canSearch(SearchBy<?> by) {
        return by != null
                && StringUtils.hasText(by.getKey())
                && !CollectionUtils.isEmpty(by.getValues());
    }

    public static boolean canFilter(FilterBy<?> by) {
        return by != null
                && StringUtils.hasText(by.getKey())
                && !CollectionUtils.isEmpty(by.getValues());
    }


}
